/**
 * Copyright 2018-2019 jianggujin (www.jianggujin.com).
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.jianggujin.modulelink.loader;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.jianggujin.modulelink.action.JActionProxyHandler;
import com.jianggujin.modulelink.action.JActionWrapper;
import com.jianggujin.modulelink.config.JModuleConfig;
import com.jianggujin.modulelink.exception.JActionExistsException;
import com.jianggujin.modulelink.exception.JActionExistsException.JDefaultActionExistsException;
import com.jianggujin.modulelink.exception.JModuleLinkException;
import com.jianggujin.modulelink.magager.JModuleManagerSupport;
import com.jianggujin.modulelink.module.JDefaultModule;
import com.jianggujin.modulelink.module.JModule;
import com.jianggujin.modulelink.module.JModuleListener;
import com.jianggujin.modulelink.support.annotation.JActionComponent;
import com.jianggujin.modulelink.support.annotation.JInject;
import com.jianggujin.modulelink.support.inject.JInjectArray;
import com.jianggujin.modulelink.support.inject.JInjectByName;
import com.jianggujin.modulelink.support.inject.JInjectByType;
import com.jianggujin.modulelink.support.inject.JInjectCollection;
import com.jianggujin.modulelink.support.inject.JInjectMap;
import com.jianggujin.modulelink.support.inject.JMixedInject;
import com.jianggujin.modulelink.support.plugin.JInterceptor;
import com.jianggujin.modulelink.support.plugin.JPlugin;
import com.jianggujin.modulelink.util.JBeanUtils;
import com.jianggujin.modulelink.util.JLogFactory;
import com.jianggujin.modulelink.util.JLogFactory.JLog;
import com.jianggujin.modulelink.util.JModuleClassLoader;
import com.jianggujin.modulelink.util.JStringUtils;

/**
 * 模块加载器解析器抽象实现
 * 
 * @author jianggujin
 *
 */
public abstract class JAbstractModuleLoaderSolver implements JModuleLoaderSolver {
    private static final JLog logger = JLogFactory.getLog(JAbstractModuleLoaderSolver.class);

    /**
     * 创建{@link JModule}
     * 
     * @param moduleManager
     * @param moduleConfig
     * @param moduleClassLoader
     * @param listeners
     * @return
     */
    protected <T extends JModuleConfig> JDefaultModule<T> createModule(JModuleManagerSupport<T> moduleManager,
            T moduleConfig, JModuleClassLoader moduleClassLoader, List<JModuleListener> listeners) {
        JDefaultModule<T> module = new JDefaultModule<T>();
        module.setListeners(listeners);
        module.setModuleClassLoader(moduleClassLoader);
        module.setModuleConfig(moduleConfig);
        return module;
    }

    /**
     * 初始化Action
     * 
     * @param moduleManager
     * @param interceptors
     * @param module
     * @param actions
     * @throws Exception
     */
    protected <T extends JModuleConfig> void initActions(JModuleManagerSupport<T> moduleManager,
            List<JInterceptor> interceptors, JDefaultModule<T> module, Collection<Object> actions) throws Exception {
        JModuleConfig moduleConfig = module.getModuleConfig();
        JMixedInject inject = new JMixedInject();
        JActionWrapper defaultAction = null;
        Map<String, JActionWrapper> actionWrappers = new HashMap<String, JActionWrapper>();
        for (Object action : actions) {
            inject.addInject(this.inject(action, module));
            JActionWrapper wrapper = this.wrapAction(action, interceptors, module);

            String actionName = wrapper.getName();
            if (JStringUtils.isBlank(actionName)) {
                throw new JModuleLinkException("JModuleLink scan actions actionName must not be null");
            }
            if (logger.isInfoEnabled()) {
                logger.info("JModuleLink scan action: {}, bean: {}", actionName, action.getClass().getCanonicalName());
            }
            if (actionWrappers.containsKey(actionName)) {
                throw new JActionExistsException("duplicated action found:{}", actionName);
            }

            if (defaultAction == null) {
                if (wrapper.isDefault()) {
                    defaultAction = wrapper;
                }
            } else if (wrapper.isDefault()) {
                throw new JDefaultActionExistsException("duplicated default action found in module:{}",
                        moduleConfig.getModuleName());
            }
            actionWrappers.put(actionName, wrapper);
        }
        if (defaultAction == null && actionWrappers.size() == 1) {
            defaultAction = actionWrappers.values().iterator().next();
        }
        module.setActionWrappers(actionWrappers);
        module.setDefaultActionWrapper(defaultAction);

        inject.inject(moduleManager.getBeanProvider(), module);
    }

    /**
     * 对Action进行处理，安装拦截器
     * 
     * @param action
     * @param interceptors
     * @param module
     * @return
     * @throws Exception
     */
    private JActionWrapper wrapAction(Object action, Collection<JInterceptor> interceptors, JModule<?> module)
            throws Exception {
        Class<?> clazz = action.getClass();
        JActionComponent component = clazz.getAnnotation(JActionComponent.class);
        String name = null;
        String description = null;
        boolean isDefault = false;
        Method executeMethod = null;
        if (component != null) {
            name = component.name();
            description = component.description();
            String[] defaultModules = component.defaultModules();
            if (defaultModules.length > 0) {
                for (String item : defaultModules) {
                    if (JStringUtils.equals(module.getModuleConfig().getModuleName(), item)) {
                        isDefault = true;
                        break;
                    }
                }
            } else {
                isDefault = component.isDefault();
            }
            String methodName = component.method();
            if (!JStringUtils.isEmpty(methodName)) {
                executeMethod = JBeanUtils.findMethod(clazz, methodName, Object.class);
            }
        }
        if (JStringUtils.isEmpty(name)) {
            name = JStringUtils.firstCharToLowerCase(clazz.getSimpleName());
        }
        if (interceptors != null) {
            for (JInterceptor interceptor : interceptors) {
                action = interceptor.install(action);
            }
        }
        Object proxy = Proxy.newProxyInstance(clazz.getClassLoader(), JPlugin.getAllInterfaces(clazz),
                new JActionProxyHandler(module, action, name));
        return new JActionWrapper(name, isDefault, description, executeMethod, proxy, clazz);
    }

    /**
     * 自动注入
     * 
     * @param target
     * @param module
     * @throws Exception
     */
    private com.jianggujin.modulelink.support.inject.JInject inject(Object target, JModule<?> module) throws Exception {
        Class<?> type = target.getClass();
        List<com.jianggujin.modulelink.support.inject.JInject> injects = new ArrayList<com.jianggujin.modulelink.support.inject.JInject>();
        while (!Object.class.equals(type) && type != null) {
            for (Field field : type.getDeclaredFields()) {
                int modifier = field.getModifiers();
                if (Modifier.isFinal(modifier) || Modifier.isStatic(modifier)) {
                    continue;
                }
                JInject inject = field.getAnnotation(JInject.class);
                if (inject != null) {
                    injects.add(this.injectBean(target, field, module, inject));
                    continue;
                }
            }
            type = type.getSuperclass();
        }
        return new JMixedInject(injects);
    }

    private com.jianggujin.modulelink.support.inject.JInject injectBean(Object target, Field field, JModule<?> module,
            JInject inject) throws Exception {
        String name = inject.value();
        // 存在指定Bean名称，按照名称注入
        if (!JStringUtils.isEmpty(name)) {
            return new JInjectByName(target, field, inject);
        }
        Class<?> fieldType = field.getType();
        if (fieldType.isArray()) {
            return new JInjectArray(target, field, inject);
        }
        if (Collection.class.isAssignableFrom(fieldType)) {
            return new JInjectCollection(target, field, inject);
        }
        if (Map.class.isAssignableFrom(fieldType)) {
            return new JInjectMap(target, field, inject);
        }
        return new JInjectByType(target, field, inject);
    }
}